'''
The tracker for the simple-torrent protocol.

@author: Hatem Oraby
'''
import random
from twisted.internet.protocol import ServerFactory, Protocol
from twisted.internet import reactor
from twisted.protocols.basic import LineOnlyReceiver
from util import Peer, TrackerCmds, TrackerResponse

announceTimeout = 5*60

class TrackerProtocol(LineOnlyReceiver):
    peerSetSize = 5 #The length of the peer set returned to a registering peer.
    
    def getRandSetOfPeers(self):
        existingPeers = self.factory.getPeers()
        sampleSize = min(len(existingPeers),self.peerSetSize)
        peersSet = []
        chosenPeersIndex = []
        for i in range(sampleSize):
            index = random.randint(0,len(existingPeers)-1)
            while index in chosenPeersIndex:
                index = random.randint(0, len(existingPeers)-1)
            chosenPeersIndex.append(index)
            peersSet.append(existingPeers[index])
        msgs = []
        for peer in peersSet:
            IP = peer.getIP()
            port = str(peer.getPort())
            response = IP + ":" + port
            response = "PEER:" + response
            msgs.append(response)
        return msgs
    
    def register(self,params):
        tag = TrackerCmds.register
        params = params.strip()
        try:
            #Parse params, but maybe IP is a URL so we should
            #look for the last ":" from behind. 
            index = params.rindex(":")#Raises ValueError if : not found
            IP = params[:index]
            port = params[index+1:]#Raises Index error if nothing exists after :
        except (ValueError,IndexError):
            response = "E04: Invalid " + tag +"  format. Format:"
            self.sendLine(response)
            response = tag + ":IP:PORT"
            self.sendLine(response)
            self.transport.loseConnection()
            return 
        try:
            port = int(port)
        except ValueError:
            response = "E05: " + tag + "Invalid port provided."
            self.sendLine(response)
            self.transport.loseConnection()
            return 
        
        peer = Peer(IP, port)
        self.factory.addPeer(peer)
        ReTag = TrackerResponse.registerResponse
        response = ReTag + ":OK"
        self.sendLine(response)
        msgs = self.getRandSetOfPeers()
        for msg in msgs:
            self.sendLine(msg)
        self.sendLine(TrackerResponse.doneSending + ":")
        self.transport.loseConnection()
        
    def lineReceived(self,line):
        try:
            command, params = line.split(":",1)
        except ValueError:
            #Wrong command sent
            response = "E01: Invalid File Format Received"
            self.sendLine(response)
            self.transport.loseConnection()
            return
        
        command = command.strip().upper()
        if command == TrackerCmds.register:
            self.register(params)
        else:
            response = "E02: Invalid command requested."
            #response += "Send 'HELP:' for list of commands"
            self.sendLine(response) 
            self.transport.loseConnection()

class TrackerFactory(ServerFactory):
    protocol = TrackerProtocol

    def __init__(self):
        self._torrentPeers = []
        self._expirePeriod = announceTimeout

    def getPeers(self):
        return self._torrentPeers

    def addPeer(self, peer):
        #Ensure that no duplicates exist.
        peerAlreadyExist = False
        for xPeer in self._torrentPeers:
            if peer.getAddr() == xPeer.getAddr():
                xPeer.callID.reset(self._expirePeriod)
                break
        else:
            peer.callID = reactor.callLater(self._expirePeriod,
                                            self._peerExpired, peer)
            self._torrentPeers.append(peer)

    def _peerExpired(self,peer):
        self._torrentPeers.remove(peer)

def main():
    f = TrackerFactory()
    reactor.listenTCP(2000,f)
    reactor.run()

if __name__ == '__main__':
    main()
